/*
 * Created on 2007-11-18
 *
 * TODO
 */
package jacky.lanlan.song.aop;

import java.lang.reflect.*;
import java.util.*;

import org.aopalliance.aop.Advice;

import com.google.inject.Injector;
import com.google.inject.Key;

import jacky.lanlan.song.aop.annotation.Advisor;
import jacky.lanlan.song.aop.annotation.Aspect;
import jacky.lanlan.song.aop.annotation.Pointcut;
import jacky.lanlan.song.closure.Judgment;
import jacky.lanlan.song.util.Assert;
import jacky.lanlan.song.util.Logger;

/**
 * AOP容器，轻量级的AOP实现，利用JDK动态代理实现AOP功能。
 * <p>
 * <b style="color:red">由于只是一个AOP容器，没有IoC容器的配合，所以需要AOP的对象必须在容器外创建好依赖关系。</b>
 * <p>
 * <em>默认提供对 Google Guice 的支持</em>，即自动对通过 Guice 装配的对象实施 AOP。
 * 
 * @author Jacky.Song
 */
public class AOPContext {

	protected static final Logger logger=Logger.getLogger();
	
	private Class<?>[] aspects;

	private Injector injector;
	
	private volatile boolean areadyConfiged;
	private volatile boolean started;
	
	// 保存Bean的Class key=class name,value=Class
	private final Map<String ,Class<?>> beanClassMap=new HashMap<String ,Class<?>> ();
	
	// 对应@Advisor内容的映射，key=符合pointcut的Bean的Method, value=对应这个Method的Advice
	private final Map<Method, AdviceChain> methodAdviceChainMap=new HashMap<Method,AdviceChain>();
	
	/**
	 * 提供配置AOP容器的Aspect，必须有 AspectJ 的@Aspect标注。
	 * 
	 * @param aspects
	 *          标注了@Aspect的配置类。
	 * @see org.aspectj.lang.annotation.Aspect
	 */
	public void addAspects(Class<?>... aspects) {
		this.aspects=aspects;
	}
	
	/**
	 * 注入 Guice Injector。
	 * 
	 * @see com.google.inject.Injector
	 */
	public void setInjector(Injector injector) {
		this.injector = injector;
	}
	
	/**
	 * 得到通过 Guice 装配的，并经过 AOPContext 添加了AOP的Bean。
	 * 
	 * @param <T>
	 *          Bean类型
	 * @param beanClass
	 *          Bean类型
	 * @return 具有AOP能力的Bean
	 * @see com.google.inject.Injector
	 */
	public <T> T getGuiceBean(Class<T> beanClass) {
		// 从 Guice 里面得到对象
		T bean=this.injector.getInstance(beanClass);
		// 生成AOP代理并返回
		return (T)this.generateAOPProxy(bean);
	}
	
	/**
	 * 得到通过 Guice 装配的，并经过 AOPContext 添加了AOP的Bean。
	 * 
	 * @param <T>
	 *          Bean类型
	 * @param key
	 *          参与Binding的Key对象
	 * @return 具有AOP能力的Bean
	 * @see com.google.inject.Injector
	 */
	public <T> T getGuiceBean(Key<T> key) {
		// 从 Guice 里面得到对象
		T bean=this.injector.getInstance(key);
		// 生成AOP代理并返回
		return (T)this.generateAOPProxy(bean);
	}
	
	/**
	 * 容器是否在运行中
	 */
	public boolean isRunning() {
		return started;
	}
	
	/**
	 * 容器是否关闭
	 */
	public boolean isClosed() {
		return !started;
	}
	
	private Map<String, Class<?>> readBeanClasses(Class<?>[] aspects) {
		Map<String ,Class<?>> beanClassMap=new HashMap<String ,Class<?>> ();
		for (Class<?> aspect : aspects) {
			Class<?> [] beanClasses=aspect.getAnnotation(Aspect.class).beans();
			for (Class<?> beanClass : beanClasses) {
				this.beanClassMap.put(beanClass.getName(), beanClass);
			}
		}
		return beanClassMap;
	}
	
	private HashMap<String,Pointcut> makeNamePointcutMap(Class<?>[] aspects) {
		HashMap<String,Pointcut> map=new HashMap<String,Pointcut>();
		for (Class<?> aspect : aspects) {
			Method[] methods=aspect.getDeclaredMethods();
			// 扫描所有的Pointcut
			for (Method method : methods) {
				if(method.isAnnotationPresent(Pointcut.class)) {
					map.put(method.getName(), method.getAnnotation(Pointcut.class));// 把依附的方法名作为pointcut名，组成映射
				}
			}
		}
		return map;
	}

	private Map<Pointcut,Set<Class<? extends Advice>>> makePointcutAdvicesMap(
			Class<?>[] aspects,Map<String,Pointcut> namePointcutMap){
		logger.debug("映射 Pointcut 到 Advice 链...");
		Map<Pointcut,Set<Class<? extends Advice>>> map=
			new HashMap<Pointcut,Set<Class<? extends Advice>>>();
		for(Class<?> aspect:aspects) {
			Method[] methods=aspect.getMethods();
			for(Method m:methods) {
				if (m.isAnnotationPresent(Advisor.class)) {// 找到有@Advisor标注的方法
					Advisor advisor=m.getAnnotation(Advisor.class);
					Set<Class<? extends Advice>> advices=new HashSet<Class<? extends Advice>>();
					// 收集所有的Advice
					advices.addAll(Arrays.asList(advisor.beforeAdvice()));
					advices.addAll(Arrays.asList(advisor.afterReturningAdvice()));
					advices.addAll(Arrays.asList(advisor.throwsAdvice()));
					advices.addAll(Arrays.asList(advisor.afterAdvice()));
					advices.add(advisor.aroundAdvice());
					
					// 和pointcut组成映射
					map.put(namePointcutMap.get(advisor.pointcut()), advices);// 根据方法名，在Map中查找对应的Pointcut
					
					if(logger.isDebugEnabled()) {
						logger.debug("符合 [" + advisor.pointcut() + "] 的 Advice 链: " + advices);
						logger.debug("");
					}
				}
			}
		}
		return map;
	}
	
	private Map<Method,List<Class<? extends Advice>>> makeMethodAdvicesMap(
			Map<Pointcut,Set<Class<? extends Advice>>> pcAdvicesMap){
		logger.debug("转换 Pointcut 为Bean方法...");
		// 组织映射 key=要拦截的实现方法 value=对应该方法的拦截器列表
		Map<Method,List<Class<? extends Advice>>> methodAdvicesMap=new HashMap<Method,List<Class<? extends Advice>>>();
		for(Pointcut pointcut:pcAdvicesMap.keySet()) {
			MethodFilter filter = MethodFilter.selectFilter.doWith(pointcut);// 选择适合这个Pointcut的MethodFilter
			// 遍历所有的Bean，找到匹配Pointcut的方法
			for(Class<?> clazz:this.beanClassMap.values()) {
				Method[] methods=clazz.getMethods();
				for (Method method : methods) {
					// 过滤出匹配Pointcut的方法
					if(filter.isMethodMatch(pointcut.expression(), method)) {
						// 累加针对这个方法的所有Advice
						if(!methodAdvicesMap.containsKey(method)) {
							methodAdvicesMap.put(method, new ArrayList<Class<? extends Advice>>());
						}
						methodAdvicesMap.get(method).addAll(pcAdvicesMap.get(pointcut));
					}
				}
			}
		}
		if(logger.isDebugEnabled()) {
			logger.debug("转换完成");
			logger.debug("方法->Advice链 映射表:");
			for(Method m:methodAdvicesMap.keySet()) {
				logger.debug(m.toGenericString()+" -> "+methodAdvicesMap.get(m));
			}
			logger.debug("");
		}
		return methodAdvicesMap;
	}
	
	private Map<Method, AdviceChain> chainingAdviceOfMethod(
			Map<Method,List<Class<? extends Advice>>> methodAdvicesMap) {
		logger.debug("分类方法的 Advice 链...");
		Map<Method, AdviceChain> methodAdviceChainMap=new HashMap<Method, AdviceChain> ();
		for(Method method:methodAdvicesMap.keySet()) {
			// 创建Advice链
			AdviceChain chain=new AdviceChain(methodAdvicesMap.get(method));
			// 存放
			methodAdviceChainMap.put(method, chain);
		}
		return methodAdviceChainMap;
	}
	
	/**
	 * 配置容器。
	 */
	private void config() {
		logger.debug("检测 Aspect 配置...");
		Assert.notEmpty(aspects, "没有提供 Aspect 配置信息");
		if(aspects!=null && aspects.length>0) {
			for (Class<?> aspect : this.aspects) {
				Assert.isTrue(aspect.isAnnotationPresent(Aspect.class), aspect.getName() + "没有@Aspect标注");
			}
		}
		
		logger.debug("开始配置 AOPContext...");
		
		// //////////////准备工作////////////////
		
		/*
		 * 第1步：提取Aspect里面所有的Pointcut，并组成Pointcut映射
		 */
		// key=@Pointcut标注的方法名 value=对应的@Pointcut
		Map<String,Pointcut> namePointcutMap=makeNamePointcutMap(aspects);
		
		/*
		 * 第2步：提取Aspect里面所有的Advice，按Pointcut分类(看一个Pointcut有多少Advice)，然后和Pointcut组成映射
		 */
		// key=pointcut value=对应的Advice列表
		Map<Pointcut,Set<Class<? extends Advice>>> pcAdvicesMap=makePointcutAdvicesMap(aspects,namePointcutMap);
		
		/*
		 * 第3步：提取所有需要AOP的Bean类型
		 */
		// key=类名 value=对应的Class
		Map<String ,Class<?>> beanClassMap=this.readBeanClasses(aspects);
		this.beanClassMap.putAll(beanClassMap);
		
		/*
		 * 第4步：根据第3步得到的Bean类型，遍历每个Bean的方法，以确定方法和Pointcut之间的映射
		 *       即：看一个方法是否匹配，匹配哪个Pointcut
		 */
		// key=被拦截的方法 value=对应的拦截器列表
		Map<Method,List<Class<? extends Advice>>> methodAdvicesMap=makeMethodAdvicesMap(pcAdvicesMap);
		
		/*
		 * 第5步：分组每个方法对应的Advice，以便组成 方法->拦截器链 映射
		 */
		// key=被拦截的方法 value=对应的拦截器链
		Map<Method, AdviceChain> methodAdviceChainMap=chainingAdviceOfMethod(methodAdvicesMap);
		
		this.methodAdviceChainMap.putAll(methodAdviceChainMap);

		// ////////////// 完成准备工作 ////////////////
		this.areadyConfiged=true;
		logger.debug("配置完成");
	}
	
	/**
	 * 启动容器。
	 */
	public void start() {
		logger.debug("启动 AOPContext...");
		if(!started) {
			if(!this.areadyConfiged) {
				config();
			}
			started=true;
			logger.debug("容器启动完毕");
		}
		else {
			logger.debug("容器已经启动");
		}
	}
	
	/**
	 * 为指定对象产生 AOP Proxy 。
	 * 
	 * @param target
	 *          需要AOP的对象
	 * @return 拥有 AOP 能力的，基于 target 的 JDK 动态代理
	 */
	public Object generateAOPProxy(Object target) {
		Assert.isTrue(this.beanClassMap.containsValue(target.getClass()),
				"[" + target.getClass()
				+ "] 没有注册，请通过@Aspect的beans属性注册该对象类型");
		Object proxy=null;
		try {
			// 对bean实施advice
			proxy=AOPUtils.weavingObject(
					new Judgment<Method>() {
						public boolean canPass(Method method) {
							return methodAdviceChainMap.containsKey(method);
						}
					},
					new AdviceHandler() {
						public Object adviseMethod(Object bean,Method classMethod, Object... args) throws Throwable {
							AdviceChain chain = methodAdviceChainMap.get(classMethod);// 查找这个方法的AdviceChain
					// 实施advice，半路拦截，最后返回 classMethod 返回值
							return AOPUtils.interceptMethod(chain,bean,classMethod,args);
						}
					},
					target);
			
			logger.debug("[" + target.getClass() + "] 的 AOP Proxy 创建完成。");
		}
		catch (Exception e) {
			logger.error("创建 AOP Proxy 时出错:", e);
			throw new RuntimeException("创建 AOP Proxy 时出错:" + e);
		}
		return proxy;
	}
	
	/**
	 * 关闭容器。
	 */
	public void shutdown() {
		if(started) {
			logger.debug("开始关闭容器");
			
			// 开始关闭容器
			aspects=null;
			this.beanClassMap.clear();
			this.methodAdviceChainMap.clear();
			
			started=false;
			logger.debug("容器正常关闭");
		}
		else {
			logger.debug("容器已经关闭");
		}
	}
	
}
